package com.imachen.ymcc.service.impl;

import com.alibaba.fastjson.JSON;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.imachen.ymcc.domain.AlipayInfo;
import com.imachen.ymcc.domain.PayFlow;
import com.imachen.ymcc.domain.PayOrder;
import com.imachen.ymcc.dto.AlipayNotifyDto;
import com.imachen.ymcc.dto.ApplyDto;
import com.imachen.ymcc.dto.PayNotifyLocalTxParamDto;
import com.imachen.ymcc.enums.GlobalExceptionCode;
import com.imachen.ymcc.result.JSONResult;
import com.imachen.ymcc.service.IAlipayInfoService;
import com.imachen.ymcc.service.IPayOrderService;
import com.imachen.ymcc.service.IPayService;
import com.imachen.ymcc.util.AssertUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

@Service
@Slf4j
public class PayServiceImpl implements IPayService {

    @Autowired
    private IPayOrderService payOrderService;

    @Autowired
    private IAlipayInfoService alipayInfoService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 根据订单编号查询支付单
     * @param orderNo
     * @return
     */
    @Override
    public JSONResult checkPayOrder(String orderNo) {
        return payOrderService.selectPayByOrderNo(orderNo) == null ? JSONResult.error():JSONResult.success();
    }

    /**
     * 调用支付API,发起支付
     * @param applyDto
     * @return
     */
    @Override
    public String apply(ApplyDto applyDto) {
        // JSR303参数校验
        // 业务校验
        PayOrder payOrder = payOrderService.selectPayByOrderNo(applyDto.getOrderNo());
        AssertUtil.isNotNull(payOrder, GlobalExceptionCode.PAY_ORDER_IS_NULL_ERROR);
        // 支付状态校验,判断订单是否为未支付
        Boolean boo = payOrder.getPayStatus() == PayOrder.WAIT_PAY_STATUS;
        AssertUtil.isTrue(boo, GlobalExceptionCode.PAY_ORDER_STATUS_ERROR);

        // 根据支付API发起支付请求
        if (payOrder.getPayType() == PayOrder.PAY_TYPE_ALIPAY){
            return alipay(applyDto,payOrder);
        }
        return null;
    }


    /**
     * 支付宝异步回调
     * @param alipayNotifyDto
     * @return
     */
    @Override
    public String alipayNotify(AlipayNotifyDto alipayNotifyDto) {
        try {
            // 把所有参数交给支付宝验签
            Map map = JSON.parseObject(JSON.toJSONString(alipayNotifyDto), Map.class);
            Boolean isSign = Factory.Payment.Common().verifyNotify(map);
            if (!isSign) {
                return "success";
            }

            // 1.校验支付状态,只有成功才进行下一步
            if (!alipayNotifyDto.getTrade_status().equals(AlipayNotifyDto.TRADE_SUCCESS)) {
                return "success";
            }
            // 2.查询支付单是否存
            PayOrder payOrder = payOrderService.selectPayByOrderNo(alipayNotifyDto.getOut_trade_no());
            if (payOrder == null) {
                return "success";
            }
            // 3.判断支付金额是否正确
            if (payOrder.getAmount().compareTo(new BigDecimal(alipayNotifyDto.getTotal_amount())) != 0) {
                return "success";
            }
            // 4.判断支付单状态,若为取消或者失败时,进行退款服务
            if (payOrder.getPayStatus().equals(PayOrder.CANCEL_PAY_STATUS) || payOrder.getPayStatus().equals(PayOrder.FAIL_PAY_STATUS)) {
                try {
                    AlipayTradeRefundResponse refundResponse = Factory.Payment.Common().refund(alipayNotifyDto.getOut_trade_no(), alipayNotifyDto.getTotal_amount());
                    if (!refundResponse.getCode().equals("10000")){
                        // 重试三次,都失败则记录日志,给管理层发消息处理
                    }else {
                        // 单独处理支付单和支付流水
                        return "success";
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 5.如果支付单状态为已完成,直接响应成功
            if (payOrder.getPayStatus().equals(PayOrder.SUCCESS_PAY_STATUS)) {
                return "success";
            }

            // 6.准备本地事务消息参数
            PayNotifyLocalTxParamDto payNotifyLocalTxParamDto = initPayNotifyLocalTxParam(payOrder, alipayNotifyDto);
            Message<String> message = MessageBuilder.withPayload(payOrder.getExtParams()).build();
            TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(
                    "tx-pay-notify-group",
                    "tx-pay-notify-topic:tx-pay-notify-tag",
                    message,
                    JSON.toJSONString(payNotifyLocalTxParamDto));
            // 7.判断消息是否发送失败
            Boolean sendOk = sendResult.getSendStatus() == SendStatus.SEND_OK;
            if (!sendOk) {
                return "fail";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
        return "success";
    }

    /**
     * 初始化异步消息本地事务消息参数
     * @param payOrder
     * @param alipayNotifyDto
     * @return
     */
    private PayNotifyLocalTxParamDto initPayNotifyLocalTxParam(PayOrder payOrder, AlipayNotifyDto alipayNotifyDto) {
        try {
            // 修改支付单支付状态
            payOrder.setPayStatus(PayOrder.SUCCESS_PAY_STATUS);
            payOrder.setUpdateTime(new Date());
            // 保存支付流水信息
            PayFlow payFlow = new PayFlow();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = simpleDateFormat.parse(alipayNotifyDto.getNotify_time());
            payFlow.setNotifyTime(date);
            payFlow.setSubject(alipayNotifyDto.getSubject());
            payFlow.setOutTradeNo(alipayNotifyDto.getOut_trade_no());
            payFlow.setTotalAmount(new BigDecimal(alipayNotifyDto.getTotal_amount()));
            payFlow.setTradeStatus(alipayNotifyDto.getTrade_status());
            payFlow.setCode(alipayNotifyDto.getCode());
            payFlow.setMsg(alipayNotifyDto.getMsg());
            payFlow.setPassbackParams(alipayNotifyDto.getPassback_params());
            payFlow.setPaySuccess(true);
            payFlow.setResultDesc(alipayNotifyDto.getBody());

            return new PayNotifyLocalTxParamDto(payOrder,payFlow);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 调用支付宝API
     * @param applyDto
     * @param payOrder
     * @return
     */
    private String alipay(ApplyDto applyDto, PayOrder payOrder) {

        log.info("调用支付宝API发起请求，订单编号：{}",payOrder.getOrderNo());
        // 查询支付宝aip信息
        AlipayInfo alipayInfo = alipayInfoService.selectOne(null);
        // 1. 设置参数（全局只需设置一次）
        Factory.setOptions(getOptions(alipayInfo));
        try {
            // 2. 发起API调用
            AlipayTradePagePayResponse response = Factory.Payment.Page().pay(payOrder.getSubject(),
                    payOrder.getOrderNo(),
                    payOrder.getAmount().toString(),
                    applyDto.getCallUrl());
            // 3. 处理响应或异常
            if (ResponseChecker.success(response)) {
                log.info("调用成功");
                return response.getBody();
            }
        } catch (Exception e) {
            log.info("调用遭遇异常，原因：" + e.getMessage());
            throw new RuntimeException(e.getMessage(), e);
        }
        return null;
    }


    private static Config getOptions(AlipayInfo alipayInfo) {
        Config config = new Config();
        config.protocol = alipayInfo.getProtocol();
        config.gatewayHost = alipayInfo.getGatewayHost();
        config.signType = alipayInfo.getSignType();
        config.appId = alipayInfo.getAppId();
        // 为避免私钥随源码泄露，推荐从文件中读取私钥字符串而不是写入源码中
        config.merchantPrivateKey = alipayInfo.getMerchantPrivateKey();
        //注：如果采用非证书模式，则无需赋值上面的三个证书路径，改为赋值如下的支付宝公钥字符串即可
        config.alipayPublicKey = alipayInfo.getAlipayPublicKey();
        //可设置异步通知接收服务地址（可选）
        config.notifyUrl = alipayInfo.getNotifyUrl();
        return config;
    }
}
